module nade.color;

import derelict.opengl.gl;

import std.math;
import std.conv;

import nade.random;


struct Color {
	real r = 0;
	real g = 0;
	real b = 0;
	real a = 1;

	static Color opCall(real r = 0, real g = 0, real b = 0, real a = 1)
	{
		Color c;
		c.r = r;
		c.g = g;
		c.b = b;
		c.a = a;
		return c;
	}
	static Color opCall(const Color rhs)
	{
		Color c;
		c.r = rhs.r;
		c.g = rhs.g;
		c.b = rhs.b;
		c.a = rhs.a;
		return c;
	}

	Color opAssign(const Color rhs) { r = rhs.r;	g = rhs.g; b = rhs.b; a = rhs.a; return this; }
	Color opAdd(Color rhs) const { return Color(r + rhs.r, g + rhs.g, b + rhs.b, a + rhs.a); }
	Color opSub(Color rhs) const { return Color(r - rhs.r, g - rhs.g, b - rhs.b, a - rhs.a); }
	Color opMul(Color rhs) const { return Color(r * rhs.r, g * rhs.g, b * rhs.b, a * rhs.a); }
	Color opDiv(Color rhs) const { return Color(r / rhs.r, g / rhs.g, b / rhs.b, a / rhs.a); }
	Color opAdd(real rhs) const { return Color(r + rhs, g + rhs, b + rhs, a + rhs); }
	Color opSub(real rhs) const { return Color(r - rhs, g - rhs, b - rhs, a - rhs); }
	Color opMul(real rhs) const { return Color(r * rhs, g * rhs, b * rhs, a * rhs); }
	Color opDiv(real rhs) const { return Color(r / rhs, g / rhs, b / rhs, a / rhs); }
	Color opAddAssign(Color rhs) { r += rhs.r; g += rhs.g; b += rhs.b; a += rhs.a; return this; }
	Color opSubAssign(Color rhs) { r -= rhs.r; g -= rhs.g; b -= rhs.b; a -= rhs.a; return this; }
	Color opMulAssign(Color rhs) { r *= rhs.r; g *= rhs.g; b *= rhs.b; a *= rhs.a; return this; }
	Color opDivAssign(Color rhs) { r /= rhs.r; g /= rhs.g; b /= rhs.b; a /= rhs.a; return this; }
	Color opAddAssign(real rhs) { r += rhs; g += rhs; b += rhs; a += rhs; return this; }
	Color opSubAssign(real rhs) { r -= rhs; g -= rhs; b -= rhs; a -= rhs; return this; }
	Color opMulAssign(real rhs) { r *= rhs; g *= rhs; b *= rhs; a *= rhs; return this; }
	Color opDivAssign(real rhs) { r /= rhs; g /= rhs; b /= rhs; a /= rhs; return this; }

	real modulusSquared() const { return r*r + g*g + b*b + a*a; }
	real modulus() const { return sqrt(modulusSquared); }

	
	GLfloat* ptr() const { GLfloat[] r = [cast(GLfloat)r, cast(GLfloat)g, cast(GLfloat)b, cast(GLfloat)a]; return r.ptr; }
	Color dup() const { return Color(r, g, b, a); }
	Color inverse() const { return Color(1 - r, 1 - g, 1 - b, 1 - a); }
	Color normal() const { return opMul(1.0/modulus); }

	real dot(Color rhs ...) const { return r*rhs.r + g*rhs.g + b*rhs.b + a*rhs.a; }

	Color blended(const Color rhs, real factor) const { return opMul(1 - factor) + rhs*factor; }
	Color withAlpha(real alpha) const { return Color(r, g, b, alpha); }

	uint toUInt()
	{
		return
			cast(uint)(r*255.0) |
			(cast(uint)(g*255.0) << 8) |
			(cast(uint)(b*255.0) << 16) |
			(cast(uint)(a*255.0) << 24)
			;
	}
	static Color fromUInt(uint rgba)
	{
		return Color(
			(rgba&0xFF)/255.0,
			((rgba >> 8)&0xFF)/255.0,
			((rgba >> 16)&0xFF)/255.0,
			((rgba >> 24)&0xFF)/255.0
			);
	}

	bool isZero()
	{
		return r >= -real.epsilon  &&  r <= real.epsilon
			&&  g >= -real.epsilon  &&  g <= real.epsilon
			&&  b >= -real.epsilon  &&  b <= real.epsilon
			&&  a >= -real.epsilon  &&  a <= real.epsilon
			;
	}

	void render() const
	{
		glColor4f(r, g, b, a);
	}
	void render(real a) const
	{
		glColor4f(r, g, b, a);
	}

	string toString() const { return '(' ~ to!(string)(r) ~ ' ' ~ to!(string)(g) ~ ' ' ~ to!(string)(b) ~ ' ' ~ to!(string)(a) ~ ')'; }

	static Color hsv(real h, real s, real v, real a = 1)
	{
		const hi = cast(int)(h/60)%6;
		const f = h/60 - hi;
		const p = v*(1 - s);
		const q = v*(1 - f*s);
		const t = v*(1 - (1 - f)*s);

		switch(hi)
		{
			case 0: return Color(v, t, p, a);
			case 1: return Color(q, v, p, a);
			case 2: return Color(p, v, t, a);
			case 3: return Color(p, q, v, a);
			case 4: return Color(t, p, v, a);
			case 5: return Color(v, p, q, a);
			default: assert(false, "invalid hue!");
		}
	}

	static Color random() { return hsv(.random(0.0, 360.0), 0.8, 0.8); }

	static const BLACK = Color(0, 0, 0, 1);
	static const WHITE = Color(1, 1, 1, 1);
	static const GREY = Color(0.5, 0.5, 0.5, 1);
	static const RED = Color(1, 0, 0, 1);
	static const ORANGE = Color(1, 0.5, 0, 1);
	static const YELLOW = Color(1, 1, 0, 1);
	static const GREEN = Color(0, 1, 0, 1);
	static const CYAN = Color(0, 1, 1, 1);
	static const BLUE = Color(0, 0, 1, 1);
	static const MAGENTA = Color(1, 0, 1, 1);
	static const LIGHT_GREY = Color(0.75, 0.75, 0.75, 1);
	static const LIGHT_RED = Color(1, 0.5, 0.5, 1);
	static const LIGHT_ORANGE = Color(1, 0.75, 0.5, 1);
	static const LIGHT_YELLOW = Color(1, 1, 0.5, 1);
	static const LIGHT_GREEN = Color(0.5, 1, 0.5, 1);
	static const LIGHT_CYAN = Color(0.5, 1, 1, 1);
	static const LIGHT_BLUE = Color(0.5, 0.5, 1, 1);
	static const LIGHT_MAGENTA = Color(1, 0.5, 1, 1);
	static const DARK_GREY = Color(0.25, 0.25, 0.25, 1);
	static const DARK_RED = Color(0.5, 0, 0, 1);
	static const DARK_ORANGE = Color(0.5, 0.25, 0, 1);
	static const DARK_YELLOW = Color(0.5, 0.5, 0, 1);
	static const DARK_GREEN = Color(0, 0.5, 0, 1);
	static const DARK_CYAN = Color(0, 0.5, 0.5, 1);
	static const DARK_BLUE = Color(0, 0, 0.5, 1);
	static const DARK_MAGENTA = Color(0.5, 0, 0.5, 1);
	static const PALE_RED = Color(1, 0.75, 0.75, 1);
	static const PALE_ORANGE = Color(1, 0.875, 0.75, 1);
	static const PALE_YELLOW = Color(1, 1, 0.75, 1);
	static const PALE_GREEN = Color(0.75, 1, 0.75, 1);
	static const PALE_CYAN = Color(0.75, 1, 1, 1);
	static const PALE_BLUE = Color(0.75, 0.75, 1, 1);
	static const PALE_MAGENTA = Color(1, 0.75, 1, 1);
}
